/**
 * 命令行服务器
 * 张勇 41204@qq.com
 */

#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#include "utils.h"
#include "unios.h"
#include "console.h"

#include "share/log.h"
#define LOGLEVEL LOG_DEBUG
#define LOGTAG   "CONSOLE"
#include "share/log4app.h"

size_t console_printf(console_t* con, const char* format, ...) {
    char buf[CONSOLE_ACK_SIZE_MAX];
	int size = 0;

	// 定义一个va_list类型的变量，用来储存单个参数
	va_list args;
	// 使args指向可变参数的第一个参数
    va_start(args, format);
	// 必须用vprintf等带V的
    size = vsnprintf(buf, CONSOLE_ACK_SIZE_MAX, format, args);
	// 结束可变参数的获取
    va_end(args);

	// 输出
	size_t txlen = 0;
	std_err_t err = con->trx->tx(con->trx, buf, size, &txlen);
	return err ? 0 : txlen;
}

void console_print_help(console_t* con, const char* cmd) {
	if(cmd) {
		// 打印指定命令的用法
		for(const console_cmd_map_t *pcm = con->cmds; pcm->cmd && pcm->handler; pcm++) {
			if(strcmp(cmd, pcm->cmd) < 0) continue;
			console_printf(con, "%16s\t%s\r\n", pcm->cmd, pcm->usage ? pcm->usage : "(No help)");
			return;
		}
	} else {
		// 打印所有命令的用法
		console_printf(con, "Supported commands:\r\n");
		for(const console_cmd_map_t *pcm = con->cmds; pcm->cmd && pcm->handler; pcm++) {
			console_printf(con, "%16s\t%s\r\n", pcm->cmd, pcm->usage ? pcm->usage : "(No help)"); 
		}
	}
}

void console_on_rx_command(void* cookie, void* receiver, size_t len, void* _data) {
	// 控制台指针
	console_t* con = (console_t*)cookie;
	const char *data = (const char*)_data;
	const char *param;
	
	// 处理命令
	for(const console_cmd_map_t *pcm = con->cmds; true; pcm++) {
		if(pcm->cmd == NULL) {
			// 遇到命令登记表结束
			if(pcm->handler) {
				// 有未知命令处理器，则调用它
				pcm->handler(con, data, NULL);
			} else {
				// 没有未知命令处理器
				console_printf(con, "\r\nInvalid command '%s' !\r\n", data);
			}
			break;
		} else if((param = strstr(data, pcm->cmd)) != NULL) {
			// 找到匹配的命令处理器，则调用它
			param = param + strlen(pcm->cmd);
			pcm->handler(con, pcm->cmd, param);
			break;
		}
	}
	
	// 如果配置了命令行提示符，则输出它
	if(con->prompt) con->trx->tx(con->trx, con->prompt, strlen(con->prompt), NULL);
}

static void console_on_rx_char(console_t* con, char ch) {
	if(!con || !con->rxbuf || !con->rxbufsize) return;

    // 遇到回车换行，则执行命令
	if(ch == '\r' || ch == '\n') {
		if(con->rxlen) {
			// 添加字符串结束符
			con->rxbuf[con->rxlen++] = '\0';
			// !!! 注意：当前在中断处理函数中，要创建一个延时处理命令的任务，在中断外处理命令，以免阻碍中断及时返回 !!!
			unios_task_dispatch_rx_msg(con->rxmsgtype, con, con->rxlen, con->rxbuf);
			// 清空接收缓冲区
			con->rxlen = 0;
		}
	}
	else if(ch < 0x20 || ch > 0x7F) {
		//log_putc('x');
	}
	else {
		if(con->rxlen >= con->rxbufsize - 1) {
			// 缓冲区满了之后，清空缓冲区，输出错误信息
			console_printf(con, "ERROR: buffer full (%d) !\r\n", con->rxlen);
			//loghex(con->rxbuf, con->rxlen);
			con->rxlen = 0;
		} else {
			// 字符存入缓冲区
			con->rxbuf[con->rxlen++] = ch;
		}
	}
}

static void _console_trx_on_rx_data(void *cookie, struct _transceiver *trx, void* buf, size_t size, size_t len, void* slice, size_t slice_size) {
	if(!cookie || !slice || !slice_size) return;
	for(size_t i=0; i < slice_size; i++) console_on_rx_char((console_t*)cookie, ((char*)slice)[i]);
}

std_err_t console_listen_on(console_t *con, transceiver_t *trx) {
	if(!con) return STD_ERR_INVALID_ARG;
	
	// 注册任务队列 RX 消息处理器
	std_err_t err = unios_task_register_rxmsg_handler(con->rxmsgtype, console_on_rx_command, con);
	if(err) return err;
	
	// 如果之前已经与其它 trx 绑定，则解除之前的绑定
	if(con->trx && con->trx != trx) {
		con->trx->on_rx_data = NULL;
		con->trx->on_rx_data_cookie = NULL;
	}
	if(!trx) return STD_ERR_OK;

	// 与 trx 建立绑定关系
	trx->on_rx_data = _console_trx_on_rx_data;
	trx->on_rx_data_cookie = con;
	con->trx = trx;

	// trx 开始异步监听
	return trx->listen(trx, true);
}

static void _ftreset(console_t* con) {
	con->trx->clear_rx_buffer(con->trx);
	con->rxlen = 0;
	if(unios_file_is_valid(con->fts.uf)) unios_file_destroy(&con->fts.uf);
	memset(&con->fts, 0, sizeof(con->fts));
}

void console_cmd_rxfileinfo(console_t* con, const char *cmd, const char *params) {
	// 之前还有文件传输任务未完成 ?
	if(unios_file_is_valid(con->fts.uf)) logw("Existing file transfer session reset !\n");
	// 初始化传输事务
	_ftreset(con);

	// 解析参数
	unsigned int mtu, win, fsize;
	int ret = sscanf(params, "%u,%u,%u,", &mtu, &win, &fsize);
	if(ret != 3 || !mtu || !win || !fsize) { console_printf(con, "ERROR: Invalid params !\r\n"); return; }

	// 提取文件名: 最后一个 ',' 后面的部分
	const char *device_path = strrchr(params, ',');
	if(!device_path) { console_printf(con, "ERROR: Invalid path !\r\n"); return; }
	device_path++;

	// 打开文件，准备写入
	std_err_t err = unios_file_open(device_path, UNIOS_FILE_OFLAG_WRITE_TRUNCATE, &con->fts.uf);
	if(err) { console_printf(con, "ERROR: Failed to open file: %s\r\n", std_err_comment(err)); return; }

	// 设置传输事务参数
	con->fts.fsize  = fsize;
	con->fts.mtu    = min(mtu, con->rxbufsize);
	con->fts.win    = 1;

	// 发回应答，传输继续
	console_printf(con, "OK:%u,%u\r\n", con->fts.mtu, con->fts.win);
	logd("rxfileinfo: %d, %s\r\n", fsize, device_path);
}

void console_cmd_rxfiledata(console_t* con, const char *cmd, const char *params) {
	// 文件传输任务未开始 ?
	if(!unios_file_is_valid(con->fts.uf)) { loge("No file transfer session !\n"); return; }

	// 解析参数
	unsigned int frmsn, frmlen, frmsum;
	int ret = sscanf(params, "%u,%u,%u,", &frmsn, &frmlen, &frmsum);
	logd("rxfiledata: %d, %d, %d\r\n", frmsn, frmlen, frmsum);
	if(ret != 3) { _ftreset(con); console_printf(con, "ERROR: Invalid params !\r\n"); return; }
	if(frmsn != con->fts.frmsn) { _ftreset(con); console_printf(con, "ERROR: Invalid frame SN !\r\n"); return; }
	if(!frmlen || frmlen > con->fts.mtu || (con->fts.fpos + frmlen) > con->fts.fsize) { _ftreset(con); console_printf(con, "ERROR: Invalid frame length !\r\n"); return; }
	
	// 阻塞式接收指定长度的数据
	rx_ret_t rxret = con->trx->rx(con->trx, con->rxbuf, frmlen, NULL, 10000, -1, NULL, RX_FLAG_NULL, NULL, RX_FLAG_NULL);
	logd("rxdata: %s\r\n", rx_ret_comment(rxret));
	if(rxret != RX_RET_FULL) { _ftreset(con); console_printf(con, "ERROR: Failed to receive frame data: %s !\r\n", rx_ret_comment(rxret)); return; }

	// 如果本帧数据校验错误，则返回错误，中止传输
	if(frmsum != sum_bytes(con->rxbuf, frmlen)) { _ftreset(con); console_puts(con, "ERROR: Frame checksum error\r\n"); return; }

	// 处理接收到的数据
	std_err_t err = unios_file_write(con->fts.uf, con->rxbuf, frmlen, -1, NULL);
	if(err) { _ftreset(con); console_puts(con, "ERROR: Failed to write file !\r\n"); return; }

	// 发送应答
	console_printf(con, "OK:%d\r\n", con->fts.frmsn);
	
	// 更新 session 状态
	con->fts.frmsn++; con->fts.fpos += frmlen;
	log_printf("\r%3u %%, %u / %u", con->fts.fpos / con->fts.fsize, con->fts.fpos, con->fts.fsize);

	// 传输完成 ?
	if(con->fts.fpos >= con->fts.fsize) { _ftreset(con); log_puts("\n"); logd("Complete !\r\n"); }
}

/// @brief 使用 transceiver_t 向 console_t 发送文件
std_err_t transceiver_send_file(transceiver_t *trx, const char *local_path, const char *remote_path, long timeout_ms, progress_callback_t cbprogress, void *cookie) {
	if(!trx || !local_path || !remote_path) return STD_ERR_INVALID_ARG;
	int ret;
    rx_ret_t rxret;
    std_err_t err = STD_ERR_FAIL;
    char cmd[128];
    size_t mtu = 4096;
    char* buf = NULL;
    unios_file_t uf = unios_file_null();

    do {
        // 打开文件
        err = unios_file_open(local_path, UNIOS_FILE_OFLAG_READ, &uf); if(err) { break; }
        // 获取文件大小
        size_t fsize = unios_file_datasize(uf);

        // 发送文件信息
		/// @param 指令格式: FILEDN mtu,win,fsize,device_path\r\n
		/// @param 指令示例: FILEDN 4096,1,1000000,/iflash/sector11\r\n
		/// @return 正常应答格式: OK:mtu,win\r\n
		/// @return 正常应答示例: OK:1024,1\r\n
		/// @exception 异常应答格式: ERROR: error message\r\n
		/// @exception 异常应答示例: ERROR: Invalid path !\r\n
        snprintf(cmd, sizeof(cmd), "FILEDN %u,%u,%u,%s\r\n", (FMTU)mtu, 1U, (FMTU)fsize, remote_path);
        rxret = transceiver_cmdack(trx, cmd, cmd, sizeof(cmd), 10000, "OK:", "ERROR:");
        if(rxret != RX_RET_OK) { loge("FILEDN failed: %s\r\n", rx_ret_comment(rxret)); err = rx_ret_to_std_err(rxret); break; }

		// 解析返回值
        unsigned int ack_mtu, ack_win;
		ret = sscanf(cmd, "OK:%u,%u\r\n", &ack_mtu, &ack_win); if(ret != 2 || !ack_mtu || ack_win != 1) { err = STD_ERR_INVALID_RESPONSE; break; }
        if (ack_mtu < mtu) mtu = ack_mtu;

        size_t remain = fsize;
        size_t fsn = 0, flen = 0; // 当前帧序号，帧数据长度
        buf = (char*)unios_malloc(mtu); if(!buf) { err = STD_ERR_NO_MEM; break; }
        if(cbprogress) cbprogress(trx, cookie, fsize, fsize - remain);

	    while(remain) {
            trx->clear_rx_buffer(trx);

            // 读取文件数据
            flen = min(remain, mtu);
            err = unios_file_read(uf, buf, flen, -1, NULL); if(err) { break; }

            // 发送帧头信息
            /// @param 指令格式: DATADN frmsn,frmlen,frmsum\r\n
            /// @param 指令示例: DATADN 35,1024,3752\r\n
            /// @return 正常应答格式: OK:fsn\r\n
            /// @return 正常应答示例: OK:35\r\n
            /// @exception 异常应答格式: ERROR: error message\r\n
            /// @exception 异常应答示例: ERROR: Invalid frame SN !\r\n
            snprintf(cmd, sizeof(cmd), "DATADN %u,%u,%u\r\n", (FMTU)fsn, (FMTU)flen, (FMTU)sum_bytes(buf, flen));
            err = trx->tx(trx, cmd, strlen(cmd), NULL); if(err) { break; }

            // 稍微延时一下再发数据
            unios_ms_delay(1);
            // 发送文件二进制数据
            err = trx->tx(trx, buf, flen, NULL); if(err) { break; }

            // 等待对端 ack
            rxret = trx->rx(trx, cmd, sizeof(cmd), NULL, 10000, -1, "OK:", RX_FLAG_STRING, "ERROR:", RX_FLAG_STRING);            
            if(rxret != RX_RET_OK) { loge("DATADN failed: %s\r\n", rx_ret_comment(rxret)); err = rx_ret_to_std_err(rxret); break; }
		    
            fsn++; remain -= flen;
		    if(cbprogress) cbprogress(trx, cookie, fsize, fsize - remain);
	    }

        if (err) break;
    } while(0);

	unios_file_destroy(&uf);
    if(buf) free(buf);
    return err;
}

std_err_t transceiver_recv_file(transceiver_t *trx, const char *local_path, long timeout_ms, progress_callback_t cbprogress, void *cookie) {
    return STD_ERR_OK;
}
